/******************************************************************************
*
* Copyright (c) 2004 Freescale Semiconductor, Inc.
* 
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
******************************************************************************/

/*!
 * \mainpage	Introduction
 *
 * \author	Motorola Semiconductor Products Sector
 * \date	2 Apr 2004
 */

/*!
 * \file	bestcomm_api.c
 *
 * Bestcomm_api.c implements most of the BestComm C API. The
 * TaskSetup() function is generated by the BestComm Task API tools
 * in capi/task_api/tasksetup_general.h as configured and included by
 * code_dma/image_rtos?/task_capi/(*).c. Other functions are defined as
 * inline in capi/bestcomm_api.h.
 */

#include "include/ppctypes.h"
#include "bestcomm_api.h"
#include "task_api/bestcomm_cntrl.h"
#include "task_api/bestcomm_api_mem.h"
#include "task_api/tasksetup_bdtable.h"

/***********************************************************************
 */
static const char* const TaskVersionString = "BestComm API v2.2 20041209";

/*
 * Hidden API data per task.
 */
 
static BDIdx BDHead[MAX_TASKS];
static BDIdx BDTail[MAX_TASKS];

/*
 * Virtual memory location of the MBAR. System registers and SRAM are
 * offset from this address.
 */
uint8 *MBarGlobal;

/*
 * Offset from MBarGlobal to get the physical memory address of the
 * MBAR. This will be zero for systems that do not use virtual memory in
 * their device driver model.
 */
sint64 MBarPhysOffsetGlobal;

/*
 * Offset to free space in SRAM after task code and buffer descriptors.
 */
uint32 SramOffsetGlobal = 0;

/*
 * This flag is false when TaskStart() has not yet been called on a
 * task newly configured by TaskSetup() or TaskStop() has been called.
 * Otherwise it is true.  It is possible that a task disabled itself
 * (transfer complete or BD ring empty) which will not show up in this
 * flag.
 *
 * It is really only useful for BD tasks that assign buffers (via
 * TaskBDAssign()) before TaskStart() or after TaskStop() are called.
 */
int TaskRunning[MAX_TASKS];

/*!
 * \brief	Get a string containing API version information.
 * \returns	Pointer to the API version string
 */
const char *TaskVersion(void)
{
	return TaskVersionString;
}

/*!
 * \brief	Initialize the API.
 * \param	MBarRef		Reference pointer to the device register memory
 *						map.
 *
 * \returns	TASK_ERR_NO_ERR on successful initialization.
 *			or TASK_ERR_API_ALREADY_INITIALIZED.
 *
 * This function is only used with physical addresses. 
 *
 * This function will also initialize API internal variables. The return
 * value TASK_ERR_API_ALREADY_INITIALIZED is intended to help determine if
 * another process has already instantiated a version of the API.
 */
int TasksInitAPI(uint8 *MBarRef)
{
	/*
	 * Copy pointer of register space to global variable.
	 * for use by other functions.
	 */
	MBarGlobal = MBarRef;
	
	/*
	 * The offset is 0 if physical and virtual are the same.
	 */
	MBarPhysOffsetGlobal = 0;
	
	/*
	 * IF API has not been initialized yet then...
	 * Make sure all BestComm interrupts are disabled and not pending.
	 * Make sure all tasks are disabled.
	 * This feature can only be put in after a way has been found to
	 * communicaticate with other processes.
	 */
	return TASK_ERR_NO_ERR;
}
 
/*!
 * \brief	Initialize the API when virtual memory is used.
 * \param	MBarRef		Reference pointer to the device register memory
 *						map.
 * \param	MBarPhys	Actual physical location of MBAR device register
 *						memory map.
 *
 * \returns	TASK_ERR_NO_ERR on successful initialization.
 *			or TASK_ERR_API_ALREADY_INITIALIZED.
 *
 * This function allows using virtual memory addresses as well as physical
 * addresses. All device registers are offset to the address supplied here,
 * so the virtual memory space should include enough space for the entire
 * register set of the device to include the SRAM space.
 *
 * This function will also initialize API internal variables. The return
 * value TASK_ERR_API_ALREADY_INITIALIZED is intended to help determine if
 * another process has already instantiated a version of the API.
 */
int TasksInitAPI_VM(uint8 *MBarRef, uint8 *MBarPhys)
{
	/*
	 * Copy pointer of register space to global variable.
	 * for use by other functions.
	 */
	MBarGlobal = MBarRef;
	MBarPhysOffsetGlobal = MBarPhys - MBarRef; 	
	
	/*
	 * If API has not been initialized yet then...
	 * Make sure all BestComm interrupts are disabled and not pending.
	 * Make sure all tasks are disabled.
	 * This feature can only be put in after a way has been found to
	 * communicaticate with other processes.
	 */
	return TASK_ERR_NO_ERR;
}
 
/*!
 * \brief	\em Deprecated
 * \param	sdma Base address of the BestComm register set
 *
 * \returns TASK_ERR_NO_ERR
 *
 * Use of this function is no longer necessary. It is retained for
 * compatibility with previous versions of the API.
 */
int TasksAttachImage(sdma_regs *sdma)
{
	return TASK_ERR_NO_ERR;
}

/*!
 * \brief	This function returns the value of the internal variable
 *			used to keep track of used space in SRAM.
 *
 * \returns The number of bytes from the beginning of SRAM to the end
 *			used space in the SRAM.
 *
 * This function will return the offset to free space in the SRAM
 * not used by the CAPI. \b Note: The returned value is based
 * on what is in TasksSetSramOffset. This function can
 * not determine what SRAM space was used by another process. There must
 * be some way external to the CAPI to keep track of SRAM space. This
 * function only returns the internal variable used to keep track of buffer
 * descriptors.
 */
uint32 TasksGetSramOffset(void)
{
	return SramOffsetGlobal;
}

/*!
 * \brief	This function stores the number of bytes from the
 *			beginning of SRAM to the end of the used space.
 *
 * \param	sram_offset	Number of bytes until the beginning of
 *						free space in the SRAM.
 *
 * This function sets the free space offset in SRAM.  It must be called
 * before setup in multi-task environments. It is the application's
 * job to determine where the free space in SRAM is. This sets the
 * base offset for the buffer descriptor variables during setup, so to
 * deallocate buffers that have already been set this function should be
 * called with a new offset.
 */
void TasksSetSramOffset(uint32 sram_offset)
{
	/*
     * Set the SramOffsetGlobal variable to be used by TaskSetup_BDTable
	 */
	SramOffsetGlobal = sram_offset;
}

/*!
 * \brief	Start an initialized task running.
 * \param	taskId	Task handle passed back from a successful TaskSetup()
 * \param	autoStartEnable Boolean for whether autostart bit is enabled.
 *							If this is set then the parameter autoStartTask
 *							defines the task to auto start.
 * \param	autoStartTask	TaskId for task to autostart. If autoStartEnable
 *							is not set then this parameter is a don't care.
 * \param	intrEnable		Boolean for interrupt enable for this task.
 * \returns	TASK_ERR_NO_ERR on success or TASK_ERR_INVALID_ARG if taskId
 *			is invalid.
 */
int TaskStart(TaskId taskId, uint32 autoStartEnable, TaskId autoStartTask,
			  uint32 intrEnable)
{
	if (intrEnable) {
		SDMA_INT_ENABLE(SDMA_INT_MASK, taskId);
	} else {
		SDMA_INT_DISABLE(SDMA_INT_MASK, taskId);
	}
	SDMA_TASK_AUTO_START(SDMA_TCR, taskId, autoStartEnable, autoStartTask)
	SDMA_TASK_ENABLE(SDMA_TCR, taskId);

	TaskRunning[taskId] = 1;
	return TASK_ERR_NO_ERR;
}

/*!
 * \brief	Stop a running task.
 * \param	taskId	Task handle passed back from a successful TaskSetup()
 * \returns	TASK_ERR_NO_ERR on success or TASK_ERR_INVALID_ARG if taskId
 *			is invalid.
 *
 * \em Note: Stopping a polling buffer descriptor task is a catastrophic
 * operation. It does not merely pause execution. Context is not
 * saved. The task's pointer into the BD ring is reset back to the
 * beginning.
 *
 * \em Note: This is not the case for the new "fall-through" BD tasks.
 * They save the BD ring pointer across stop/start boundaries. The
 * previous polling tasks are considered deprecated.
 */
int TaskStop(TaskId taskId)
{
	SDMA_INT_DISABLE(SDMA_INT_MASK, taskId);
	SDMA_TASK_DISABLE(SDMA_TCR, taskId);

	TaskRunning[taskId] = 0;
	return TASK_ERR_NO_ERR;
}

/*!
 * \brief Assign a buffer to a buffer descriptor.
 * \param taskId	Task handle passed back from a successful TaskSetup()
 * \param buffer0	A buffer to send data from or receive data into a device
 * \param buffer1	A second buffer to send data from or receive data into
 *					a device for use with double-buffer tasks.
 * \param size		Size of the buffer in bytes.
 * \param bdFlags	Buffer descriptor flags to set. Used by ethernet BD tasks.
 * \returns Handle to the buffer descriptor used by this DMA transfer.
 *			Error is indicated by a negative return value (see TaskErr_t).
 *
 * This function is used for both transmit and receive buffer descriptor
 * tasks. The buffer may be freed by the TaskBDRelease() function.
 * In the case of tasks with a buffer descriptor with two buffer pointers
 * this function uses both buffer0 and buffer1 where buffer0 is a source
 * and buffer1 is a destination. When the buffer descriptor is a single
 * pointer type, the buffer0 is the only pointer used and buffer1 is ignored.
 *
 * Using this function on non-buffer descriptor tasks will produce
 * unpredictable results.
 */
BDIdx TaskBDAssign(TaskId taskId, void *buffer0, void *buffer1, int size, uint32 bdFlags)
{
	BDIdx		*bdHead;
	TaskBD_t	*bd;
	BDIdx		r = TASK_ERR_NO_ERR;

	if (TaskBDIdxTable[taskId].currBDInUse == TaskBDIdxTable[taskId].numBD) {
		/*
		 * The buffer ring is full.
		 */
		r = TASK_ERR_BD_RING_FULL;

	} else if (   (TaskBDIdxTable[taskId].apiConfig & API_CONFIG_BD_FLAG)
			   && ((uint32)size & (uint32)(~SDMA_DRD_MASK_LENGTH))) {
		r = TASK_ERR_SIZE_TOO_LARGE;

	} else if (   !(TaskBDIdxTable[taskId].apiConfig & API_CONFIG_BD_FLAG)
			   && ((uint32)size & (uint32)(0xffffffff<<SDMA_BD_BIT_READY))) {
		r = TASK_ERR_SIZE_TOO_LARGE;

	} else {
		bdHead = &BDHead[taskId];

		/*
		 * Increase Buffer Descriptor in-use variable.
		 */
		++TaskBDIdxTable[taskId].currBDInUse;

		/*
		 * Get a generic TaskBD_t pointer to the BD to be assigned.
		 * Assign the buffer pointers.
		 */
		bd = TaskBDIdxTable[taskId].BDTablePtr;
		if (TaskBDIdxTable[taskId].numPtr == 1) {
			bd = (TaskBD_t *)&(((TaskBD1_t *)bd)[*bdHead]);

			((TaskBD1_t *)bd)->DataPtr[0] = (uint32)buffer0;
		} else {
			bd = (TaskBD_t *)&(((TaskBD2_t *)bd)[*bdHead]);

			((TaskBD2_t *)bd)->DataPtr[0] = (uint32)buffer0;
			((TaskBD2_t *)bd)->DataPtr[1] = (uint32)buffer1;
		}


		if (bd->Status & SDMA_BD_MASK_READY) {
			/*
			 * This BD is in use.
			 */
			r = TASK_ERR_BD_BUSY;

		} else {

			/*
			 * Set status bits and length. As soon as Status is written, the
			 * BestComm may perform the transfer.
			 */
			if (TaskBDIdxTable[taskId].apiConfig & API_CONFIG_BD_FLAG) {
				bd->Status = ( ((uint32)SDMA_DRD_MASK_FLAGS  & bdFlags)
							 | ((uint32)SDMA_DRD_MASK_LENGTH & (uint32)size)
							 | ((uint32)SDMA_BD_MASK_READY));
			} else {
				bd->Status = ( ((uint32)SDMA_BD_MASK_SIGN    & (uint32)size)
							 | ((uint32)SDMA_BD_MASK_READY));
			}

			/*
			 * Return the current BD index and increment.
			 */
			r = *bdHead;
			*bdHead = (BDIdx)((*bdHead + 1) % (BDIdx)TaskBDIdxTable[taskId].numBD);
		}
	}

	/*
	 * Reenable a fall-through BD tasks that might have exited.
	 */
	if (TaskRunning[taskId]) {
		SDMA_TASK_ENABLE(SDMA_TCR, taskId);
	}
	return r;
}

/*!
 * \brief Release last buffer in the buffer descriptor ring.
 * \param taskId	Task handle passed back from a successful TaskSetup()
 *
 * \returns Buffer descriptor index of next buffer index that will be released
 *			by another call of this function.
 *			TASK_ERR_BD_RING_EMPTY is returned if the ring is already empty.
 *
 * This function allows the system to reallocate the memory used by
 * the buffer. It also cleans up the structure in the BD ring by
 * removing the tail buffer in the ring. The buffer descriptor tasks
 * are designed around this. Non-BD tasks do not use this function.
 *
 * Using this function on non-buffer descriptor tasks will produce
 * unpredictable results.
 */
BDIdx TaskBDRelease(TaskId taskId)
{
	BDIdx		*bdTail;
	TaskBD_t	*bd;

	bdTail = &BDTail[taskId];

	if (TaskBDIdxTable[taskId].currBDInUse == 0) {
		/*
		 * Buffer Descriptor ring is empty, Can't Release!
		 */
		 return TASK_ERR_BD_RING_EMPTY;
	}

	/*
	 * Get a generic TaskBD_t pointer to the next BD to be released.
	 */
	bd = TaskGetBD(taskId, *bdTail);

	/*
	 * Verify the ready bit is clear.
	 */
	if (bd->Status & SDMA_BD_MASK_READY) {
		return TASK_ERR_BD_BUSY;
	}

	/*
	 * Increment the tail pointer around the ring, decrement in-use.
	 */
	*bdTail = (BDIdx)((*bdTail + 1) % (BDIdx)TaskBDIdxTable[taskId].numBD);
	--TaskBDIdxTable[taskId].currBDInUse;

	return *bdTail;
}


/*!
 * \brief Release all buffers.
 * \param taskId	Task handle passed back from a successful TaskSetup()
 *
 * \returns Buffer descriptor index of next buffer that will be
 *			assigned by TaskBDAssign() which will also be the first
 *			released by the next call to TaskBDRelease() or
 *			TASK_ERR_TASK_RUNNING if the task has not been stopped.
 *
 * This function is similar to TaskBDRelease() except that it releases
 * all assigned buffers including those not yet processed by the BestComm
 * task; i.e. SDMA_BD_MASK_READY is set.
 * Non-BD tasks do not use this
 * function.
 *
 * The task should not be running. Call TaskStop() first.
 *
 * \em Note: Partially transmitted buffers are up to the user to handle.
 *
 * Using this function on non-buffer descriptor tasks will produce
 * unpredictable results.
 */
BDIdx TaskBDReset(TaskId taskId)
{
	BDIdx		i;
	TaskBD_t	*bd, *bdTab;

	if (TaskRunning[taskId]) {
		return TASK_ERR_TASK_RUNNING;
	}

	bdTab = TaskBDIdxTable[taskId].BDTablePtr;

	for (i = (BDIdx)TaskBDIdxTable[taskId].numBD - 1; i >= 0; --i) {

		if (TaskBDIdxTable[taskId].numPtr == 1) {
			bd = (TaskBD_t *)&(((TaskBD1_t *)bdTab)[i]);
		} else {
			bd = (TaskBD_t *)&(((TaskBD2_t *)bdTab)[i]);
		}

		bd->Status = 0x0;
	}

	TaskBDIdxTable[taskId].currBDInUse = 0;
	*TaskBDIdxTable[taskId].BDStartPtr =
		(volatile uint32)((volatile uint32)TaskBDIdxTable[taskId].BDTablePtr
						+ MBarPhysOffsetGlobal);
	return BDHead[taskId] = BDTail[taskId] = 0;
}


/*!
 * \brief	Return BestComm debug information.
 * \param	taskId	Task handle passed back from a successful TaskSetup()
 * \param	paramSet	TBD
 * \returns	TBD
 *
 * The implementation of this function is yet to be determined.
 */
int TaskDebug(TaskId taskId, TaskDebugParamSet_t *paramSet)
{
	if ((taskId < 0) || (taskId >= MAX_TASKS)) {
		return TASK_ERR_INVALID_ARG;
	}
	if (paramSet == NULL) {
		return TASK_ERR_INVALID_ARG;
	}

	return TASK_ERR_NO_ERR;
}
